#include "apue.h"
#include <pthread.h>
#include <sys/time.h>

static pthread_cond_t cond_sleep= PTHREAD_COND_INITIALIZER;
static pthread_mutex_t lock_sleep= PTHREAD_MUTEX_INITIALIZER;
static int calls;

int timeout(struct timeval *start, unsigned int sec);

static void sig_alrm(int signo) {
    if (calls > 0) {
        pthread_cond_broadcast(&cond_sleep);
        alarm(1);
    }
}

unsigned int sleep(unsigned int secs) {
    struct sigaction newact, oldact;
    sigset_t newmask, oldmask;
    struct timeval start; 
    
    gettimeofday(&start, NULL);

    pthread_mutex_lock(&lock_sleep);

    calls++;

    sigaction(SIGALRM, NULL, &oldact);
    if (oldact.sa_handler != sig_alrm) {
        newact.sa_handler= sig_alrm;
        sigemptyset(&newact.sa_mask);
        newact.sa_flags= 0; 
        sigaction(SIGALRM, &newact, &oldact);
    }
    
    if (calls == 1)
        alarm(1);

    newmask= oldmask;  
    sigdelset(&newmask, SIGALRM);
    pthread_sigmask(SIG_UNBLOCK, &newmask, &oldmask);

    while (!timeout(&start, secs))
        pthread_cond_wait(&cond_sleep, &lock_sleep);
     
    calls--;

    pthread_mutex_unlock(&lock_sleep);
    pthread_sigmask(SIG_SETMASK, &oldmask, NULL);

    return 0;
}

int timeout(struct timeval *start, unsigned int sec) {
    struct timeval cur; 
    gettimeofday(&cur, NULL);
    
    return (start->tv_sec + sec == cur.tv_sec && start->tv_usec < cur.tv_usec)
            || (start->tv_sec + sec < cur.tv_sec);
}

void *thr_fn(void *arg) {
    unsigned long sec= (unsigned long)arg;
    printf("I will sleep %ld sec.\n", sec);
    sleep(sec);
    printf("end after %ld sec.\n", sec);
    return 0;
}

int main(void) {
    pthread_t tid1, tid2, tid3;
    void *retval;
    pthread_create(&tid1, NULL, thr_fn, (void *)2);
    pthread_create(&tid2, NULL, thr_fn, (void *)3);
    pthread_create(&tid3, NULL, thr_fn, (void *)4);

    pthread_join(tid1, &retval);
    pthread_join(tid2, &retval);
    pthread_join(tid3, &retval);
    return(0);
}
